MoneyBag.java
package money;
import java.util.ArrayList;
import java.util.List;
/**
* A MoneyBag defers exchange rate conversions.
*
* For example adding 12 Swiss Francs to 14 US Dollars is represented
* as a bag containing the two Monies 12 CHF and 14 USD. Adding another
* 10 Swiss francs gives a bag with 22 CHF and 14 USD. Due to the
* deferred exchange rate conversion we can later value a MoneyBag with
* different exchange rates.
*
* @author Kent Beck
* @author Robert Duvall (small updates and commenting)
*/
public class MoneyBag extends Money {
private final List<SimpleMoney> fMonies;
/**
* Static "constructor" that creates appropriate concrete class based on the given parameters
*
* @param monies any number of simple monies to combine
*/
public static Money of (SimpleMoney ... monies) {
MoneyBag result = new MoneyBag();
for (SimpleMoney m : monies) {
result.appendTo(m);
}
return result.simplify();
}
/**
* Constructs an empty bag of money.
*/
public MoneyBag () {
fMonies = new ArrayList<>(5);
}
/**
* Constructs a bag of money from the contents of the given bag of money.
*/
public MoneyBag (MoneyBag copy) {
fMonies = new ArrayList<>(copy.fMonies);
}
/**
* @see Money#add(Money)
*
* Forwards the request to addMoney helper, since we know what concrete type this instance is
*/
@Override
public Money add (Money m) {
return m.addMoneyBag(this);
}
/**
* @see Money#subtract(Money)
*/
@Override
public Money subtract (Money m) {
return add(m.negate());
}
/**
* @see Money#multiply(int)
*/
@Override
public Money multiply (int factor) {
MoneyBag result = new MoneyBag();
if (factor != 0) {
for (SimpleMoney each : fMonies) {
// multiply returns Money abstraction, but we know it is SimpleMoney
result.appendTo((SimpleMoney)each.multiply(factor));
}
}
return result;
}
/**
* @see Money#negate()
*/
@Override
public Money negate () {
MoneyBag result = new MoneyBag();
for (SimpleMoney each : fMonies) {
// negate returns Money abstraction, but we know it is SimpleMoney
result.appendTo((SimpleMoney)each.negate());
}
return result;
}
/**
* @see Money#isZero()
*/
@Override
public boolean isZero () {
return fMonies.size() == 0;
}
/**
* @see Object#equals(Object)
*/
@Override
public boolean equals (Object anObject) {
if (isZero()) {
return anObject instanceof Money && ((Money)anObject).isZero();
}
if (anObject instanceof MoneyBag aMoneyBag) {
if (aMoneyBag.fMonies.size() != fMonies.size()) {
return false;
}
for (SimpleMoney each : fMonies) {
if (!aMoneyBag.contains(each)) {
return false;
}
}
return true;
}
return false;
}
/**
* @see Object#hashCode()
*/
@Override
public int hashCode () {
int hash = 0;
for (SimpleMoney each : fMonies) {
hash ^= each.hashCode();
}
return hash;
}
/**
* @see Object#toString()
*/
@Override
public String toString () {
StringBuilder sb = new StringBuilder();
sb.append("{");
for (SimpleMoney each : fMonies) {
sb.append(each);
}
sb.append("}");
return sb.toString();
}
// Double dispatch methods and their helpers --- one needed for each different subclass!
@Override
Money addMoney (SimpleMoney simple) {
MoneyBag result = new MoneyBag(this);
result.appendTo(simple);
return result.simplify();
}
private void appendTo (SimpleMoney aSimpleMoney) {
if (aSimpleMoney.isZero()) {
return;
}
Money old = findMoney(aSimpleMoney.currency());
if (old == null) {
fMonies.add(aSimpleMoney);
return;
}
fMonies.remove(old);
SimpleMoney sum = (SimpleMoney)old.add(aSimpleMoney);
if (sum.isZero()) {
return;
}
fMonies.add(sum);
}
@Override
Money addMoneyBag (MoneyBag bag) {
MoneyBag result = new MoneyBag(this);
result.appendTo(bag);
return result.simplify();
}
private void appendTo (MoneyBag aBag) {
for (SimpleMoney each : aBag.fMonies) {
appendTo(each);
}
}
// Find first money in this bag matching given currency
private SimpleMoney findMoney (String currency) {
for (SimpleMoney each : fMonies) {
if (each.currency().equals(currency)) {
return each;
}
}
return null;
}
// Returns true only if given money is in this bag.
private boolean contains (SimpleMoney m) {
SimpleMoney found = findMoney(m.currency());
return found != null && found.amount() == m.amount();
}
// Simplifies this money bag if it is not needed
private Money simplify () {
if (fMonies.size() == 1) {
return fMonies.iterator().next();
}
return this;
}
}